home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / mail / stripmsg < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  37.0 KB  |  1,004 lines

  1. #!/usr/local/bin/gawk -f
  2. #!/u/johnd/bin/gawk -f
  3. #!/usr/bin/awk -f
  4. # @(#) stripmsg.gawk 2.5 96/09/26
  5. # 1988-90  john h. dubois iii (john@armory.com)
  6. # 12/01/90 This script was originally written to run under DOS.
  7. #          Cleaned it up, made it work under UNIX.
  8. #          Works w/both bsd mail and mailx files.
  9. # 91/02/17 Added more mail fields.
  10. # 91/04/17 Changed message to write to stderr instead of /dev/tty,
  11. #          Added more mail fields
  12. # 91/07/25 Made message separators explicit,
  13. #          so From must be followed by a space
  14. # 92/02/16 Added help.
  15. # 92/05/02 Made into #!awk script
  16. # 93/07/28 Changed name to stripmail to avoid conflict with procmail.
  17. # 94/03/09 Use gawk so - options can be given
  18. # 94/03/19 Added unwanted news header names to DiscardFields.
  19. #          Always get rid of empty header fields.
  20. # 95/09/01 Improved help.  Added b and M options.  Ignore header case.
  21. # 96/06/04 Added i option.
  22. # 96/06/17 Process news messages too.  Changed name to stripmsg.
  23. #          Added dqV options.  Strip empty lines at end of message.
  24. # 96/07/01 Added I option.
  25. # 96/09/26 Fixed usage of ReqFields[]
  26.  
  27. BEGIN {
  28.     Name = "stripmsg"
  29.     Usage = "Usage: " Name " [-dhiIbqMV] [message-file ...]"
  30.     ARGC = Opts(Name,Usage,"dhiIbqMV",0)
  31.  
  32.     DiscardFieldsList = "Return-Path:, X-Mailer:, Received:, Status:, "\
  33.     "Message-ID:, References:, Reference:, Mime-Version:, "\
  34.     "X-Newsgroups:, X-Organization:, X-Internet:, Resent-Message-Id:, "\
  35.     "X-Envelope-To:, X-Vms-To:, Distribution:, Errors-To:, Lines:, "\
  36.     "Summary:, Expires:, Followup-To:, Keywords:, X-Sender:, "\
  37.     "Content-Type:, Content-Length:, Path:, NNTP-Posting-Host:, "\
  38.     "X-Newsreader:, Organization:, Sender:, Precedence:, "\
  39.     "Content-Transfer-Encoding:, Content-Disposition:"
  40.  
  41.     OptFieldsList = "Cc:, Bcc:, In-Reply-To:, Apparently-To:, To:, "\
  42.     "Really-From:, Newsgroups:, Posted-Date:, "\
  43.     "Phone-Number:, Address:, In-Real-Life:, Reply-To:, Source-Info:"
  44.  
  45.     ReqFieldsList = "From, From:, Subject:, Date:"
  46.  
  47.     if ("h" in Options) {
  48.     printf \
  49. "%s: remove unwanted header fields from files of mail or news messages.\n"\
  50. "%s\n"\
  51. "Unwanted header lines are removed from the messages in the named files,\n"\
  52. "and blank lines at the end of messages are removed.\n"\
  53. "Output is sent to the standard output.\n"\
  54. "Messages may be separated from each other either with mailx separators\n"\
  55. "(lines that consist of four control-A characters), or by lines that begin\n"\
  56. "with 'From ' (as in non-mailx mail files), or lines that begin with\n"\
  57. "'Article ' or 'Article: ' (as in a typical file of saved News messages).\n"\
  58. "%s will complain about any messages that do not contain all of the\n"\
  59. "following fields: %s\n"\
  60. "All of these fields are discarded, along with any empty fields:\n"\
  61. "%s\n"\
  62. "The following fields (in addition to the required fields) are allowed, and\n"\
  63. "are not discarded:\n"\
  64. "%s\n"\
  65. "Other fields are retained by default.\n"\
  66. "Options:\n"\
  67. "-h: Print this help.\n"\
  68. "-b: Discard mailx message separators or Article lines, depending on file\n"\
  69. "    type.  This will convert a mailx file to a \"From \"-separated file if\n"\
  70. "    the first line of each message after the mailx separator is a\n"\
  71. "    \"From \" line.  A News file will end up with messages separated by\n"\
  72. "    whatever the next header line after the Article line is, typically a\n"\
  73. "    From: line.\n"\
  74. "-d: Discard any fields not named explicitly above.\n"\
  75. "-M: Write '^A^A^A^A'-separated messages regardless of the input format.\n"\
  76. "-i: Clean up an included message.  If the text passed to %s begins with\n"\
  77. "    a header, that header is ignored, and the next header found\n"\
  78. "    (recognized by starting with a line reading 'From ') is processed\n"\
  79. "    instead.  Only 'From ', not ^A^A^A^A, is recognized as the start of\n"\
  80. "    both the initial and included header, and only one header is processed.\n"\
  81. "-I: Clean up a single indented message.  The indent string is taken to be\n"\
  82. "    the part of the first line of input up to the field name (the first\n"\
  83. "    word that ends with a colon).  If the first line begins immediately\n"\
  84. "    with a field name, the indent string is null.  The indent string is\n"\
  85. "    removed from each subsequent line that begins with it before checking\n"\
  86. "    for field names, header end, etc.  The indent string is not removed\n"\
  87. "    from the line if it is printed.  The start of the message is not\n"\
  88. "    searched for; it is assumed that nothing comes before the message\n"\
  89. "    header.\n"\
  90. "-q: Quiet operation.  Do not complain about header problems.\n"\
  91. "-V: Verbose operation: Tell what the file type is.\n",
  92. Name,Usage,Name,ReqFieldsList,WrapLine(DiscardFieldsList,76,"   ","^ "),
  93. WrapLine(OptFieldsList,76,"   ","^ "),Name,Name
  94.     exit(0)
  95.     }
  96.     if ((Err = ExclusiveOptions("i,bM;I,bmi",Options)) != "") {
  97.     printf "Error: %s\n",Err > "/dev/stderr"
  98.     Err = 1
  99.     exit(1)
  100.     }
  101.     MakeSet(s,ReqFieldsList,", ")
  102.     for (fieldName in s) {
  103.     canonReqFields[tolower(fieldName)] = fieldName
  104.     foundReqFields[tolower(fieldName)] = 0
  105.     }
  106.     MakeSet(DiscardFields,tolower(DiscardFieldsList),", ")
  107.     MakeSet(OptFields,tolower(OptFieldsList),", ")
  108.     FromOut = "b" in Options
  109.     MailxOut = "M" in Options
  110.     ProcInclude = "i" in Options
  111.     ProcIndent = "I" in Options
  112.     SingleMessage = ProcInclude || ProcIndent
  113.     DiscardUnknown = "d" in Options
  114.     Verbose = "V" in Options
  115.     Quiet = "q" in Options
  116. }
  117.  
  118. # Determine the message file type from the separator on the first line
  119. NR == 1 {
  120.     if (ProcIndent) {
  121.     IndentStr = $0
  122.     if (!sub(/[^ \t]+:[ \t].*$/,"",IndentStr)) {
  123.         printf "First line has no header field.  Aborting.\n" > \
  124.         "/dev/stderr"
  125.         exit 1
  126.     }
  127.     IndentLen = length(IndentStr)
  128.     Header = 1
  129.     DoLine()
  130.     next
  131.     }
  132.     if (ProcInclude) {
  133.     msep = "^From "
  134.     print $0
  135.     next    # If the input started with a header, skip it.
  136.     }
  137.     if ($1 == "\1\1\1\1") {
  138.     if (Verbose)
  139.         print "File type is mailx." > "/dev/stderr"
  140.     msep = "^\1\1\1\1"
  141.     MailxOut = 0    # No translation from mailx needed
  142.     }
  143.     else if ($1 ~ "^(From|Article:?)$") {
  144.     if (Verbose)
  145.         print "Messages are \"" $1 " \" separated." > "/dev/stderr"
  146.     msep = "^" $1 " "
  147.     if ($1 == "From")
  148.         FromOut = 0    # No translation from "From " needed
  149.     }
  150.     else {
  151.     print \
  152. "Unrecognized file type: messages must begin with \"^A^A^A^A\", \"From \",\n"\
  153. "or \"Article\"." > "/dev/stderr"
  154.     exit 1
  155.     }
  156. }
  157.  
  158. !Header && !NoMore && $0 ~ msep {    # Found start of a new message
  159.     Header = 1
  160.     start = NR
  161.     LastField = ""
  162.     if (FromOut)
  163.     next    # We hope that the message will have a 'From ' line!
  164.     else if (MailxOut)
  165.     print "\1\1\1\1\n\1\1\1\1"
  166.     # Print the field sep here, because if it's mailx it would be discarded
  167.     # later since it has no value attached
  168.     print $0
  169.     next
  170. }
  171.  
  172. {
  173.     DoLine()
  174. }
  175.  
  176. function DoLine() {
  177.     if (Header) {
  178.     OutLine = $0
  179.     # Get rid of indent string
  180.     if (ProcIndent && substr($0,1,IndentLen) == IndentStr)
  181.         $0 = substr($0,IndentLen+1)
  182.     if (NF)
  183.         CheckHeaderLine()
  184.     else {        # At end of header
  185.         EndHeader()
  186.         print OutLine    # Header/body separator; null or indent string
  187.     }
  188.     }
  189.     else    # In body
  190.     DoBody()
  191. }
  192.  
  193. # The only body processing is cleaning up trailing whitespace
  194. function DoBody() {
  195.     if ($0 ~ /^[ \t]*$/)
  196.     Whitespace = Whitespace $0 "\n"
  197.     else {
  198.     if (Whitespace) {
  199.         printf "%s",Whitespace
  200.         Whitespace = ""
  201.     }
  202.     print $0
  203.     }
  204. }
  205.  
  206. function CheckHeaderLine() {
  207.     if ($0 ~ "^[ \t]") {    # Found field continuation line
  208.     Field = LastField
  209.     Continued = 1
  210.     }
  211.     else {
  212.     Field = $1
  213.     Continued = 0
  214.     }
  215.     LastField = Field
  216.     lcaseField = tolower(Field)
  217.     # Get rid of unwanted fields and those that have a field name but no value
  218.     if (lcaseField in DiscardFields || !Continued && NF == 1)
  219.     next
  220.     if (lcaseField in foundReqFields) {
  221.     foundReqFields[lcaseField] = 1
  222.     print OutLine
  223.     }
  224.     else if (lcaseField !~ /:$/) {
  225.     print OutLine
  226.     if (!Quiet)
  227.         printf "Non-header line found while still in header on line %d.\n"\
  228.     "Forcing switch from header to body processing.  Line follows:\n%s\n",
  229.     NR,OutLine > "/dev/stderr"
  230.     EndHeader()
  231.     next
  232.     }
  233.     else if (lcaseField in OptFields || !DiscardUnknown)
  234.     print OutLine
  235. }
  236.  
  237. function EndHeader(  fieldName) {
  238.     # Check whether we found all required headers.
  239.     for (fieldName in foundReqFields)
  240.     if (foundReqFields[fieldName] != 0)
  241.         foundReqFields[fieldName] = 0
  242.     else if (!Quiet)
  243.         printf "No \"%s\" field for message starting on line %d.\n",
  244.         canonReqFields[fieldName],start > "/dev/stderr"
  245.     Header = 0
  246.     Whitespace = ""
  247.     if (SingleMessage)    # Only process one header
  248.     NoMore = 1
  249. }
  250.  
  251. ### End main program
  252. ### Begin library routines
  253.  
  254. # MakeSet: make a set from a list.
  255. # An index with the name of each element of the list
  256. # is created in the given array.
  257. # Input variables: 
  258. # Elements is a string containing the list of elements.
  259. # Sep is the character that separates the elements of the list.
  260. # Output variables:
  261. # Set is the array.
  262. func MakeSet(Set,Elements,Sep,Num,Names) {
  263.     Num = split(Elements,Names,Sep)
  264.     for (; Num; Num--)
  265.     Set[Names[Num]]
  266. }
  267.  
  268. ### Begin SplitLine routines
  269.  
  270. # Splits Line into lines with maximum length of MaxLen.
  271. # If WordChars is non-empty, lines are split on characters other than
  272. # those in the set it describes, if possible.
  273. # The default for WordChars is:  "a-zA-Z0-9_"
  274. # The lines are put into Lines with indices starting with 1.
  275. # The number of lines put in Lines is returned.
  276. function SplitLine(Line,Lines,MaxLen,WordChars,
  277. Len,LineNum,MaxLine) {
  278.     if (WordChars == "")
  279.     WordChars = "a-zA-Z0-9_"
  280.     WordChars = "[" WordChars "]"
  281.     LineNum = 0
  282.     while (Len = length(Line)) {
  283.     Lines[++LineNum] = substr(Line,1,MaxLen)
  284.     if (substr(Line,MaxLen,2) ~ "^" WordChars WordChars) {
  285.         MaxLine = Lines[LineNum]
  286.         # If the line was split in a word, get rid of the trailing
  287.         # word-part of the line.
  288.         sub(WordChars "*$","",MaxLine)
  289.         # Check whether line consists solely of whitespace, but do not
  290.         # discard it for now because we need to know how much of the
  291.         # line we have processed.
  292.         if (MaxLine !~ "^[ \t]+$")
  293.         Lines[LineNum] = MaxLine
  294.     }
  295.     # Get rid of whatever part of the line was just put into Lines[]
  296.     Line = substr(Line,length(Lines[LineNum]) + 1)
  297.     # Get rid of leading & trailing whitespace
  298.     gsub("^[ \t]+|[ \t]+$","",Lines[LineNum])
  299.     }
  300.     return LineNum
  301. }
  302.  
  303. # WrapLine: insert newlines into string S to convert it into multiple
  304. # lines of length Len.  A newline is not included at the end.
  305. # If WordChars is non-empty, lines are split on characters other than
  306. # those in the set it describes, if possible.
  307. # The default for WordChars is:  "a-zA-Z0-9_"
  308. # String Indent, which may be null, is prefixed to all lines.
  309. # It is not included in the calculation of Len; the returned strings
  310. # will have a maximum length of Len + length(Indent).
  311. function WrapLine(S,Len,Indent,WordChars,   NumLines,Lines,LineNum,Fmtd) {
  312.     NumLines = SplitLine(S,Lines,Len,WordChars)
  313.     for (LineNum = 1; LineNum < NumLines; LineNum++)
  314.     Fmtd = Fmtd Indent Lines[LineNum] "\n"
  315.     Fmtd = Fmtd Indent Lines[LineNum]
  316.     return Fmtd
  317. }
  318.  
  319. ### End SplitLine routines
  320. ### Start of ProcArgs library
  321. # @(#) ProcArgs 1.11 96/12/08
  322. # 92/02/29 john h. dubois iii (john@armory.com)
  323. # 93/07/18 Added "#" arg type
  324. # 93/09/26 Do not count -h against MinArgs
  325. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  326. #          Removed meaning of "+" or "-" by itself.
  327. # 94/03/08 Added & option and *()< option types.
  328. # 94/04/02 Added NoRCopt to Opts()
  329. # 94/06/11 Mark numeric variables as such.
  330. # 94/07/08 Opts(): Do not require any args if h option is given.
  331. # 95/01/22 Record options given more than once.  Record option num in argv.
  332. # 95/06/08 Added ExclusiveOptions().
  333. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  334. #          Expand $VARNAME at the start of its filenames.
  335. #          Let varname=0 and -option- turn off an option.
  336. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  337. #          of the vars should be searched for in the environment.
  338. #          Check for duplicate rcfiles.
  339. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  340. #          now return various negatives values on error, not just -1, and
  341. #          Opts() may set Err to various positive values, not just 1.
  342. #          Added AllowUnrecOpt.
  343. # 96/05/23 Check type given for & option
  344. # 96/06/15 Re-port to awk
  345. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  346. #          used by other functions.
  347. # 96/10/15 Added OptChars
  348. # 96/11/01 Added exOpts arg to Opts()
  349. # 96/11/16 Added ; type
  350. # 96/12/08 Added Opt2Set() & Opt2Sets()
  351. # 96/12/27 Added CmdLineOpt()
  352.  
  353. # optlist is a string which contains all of the possible command line options.
  354. # A character followed by certain characters indicates that the option takes
  355. # an argument, with type as follows:
  356. # :    String argument
  357. # ;    Non-empty string argument
  358. # *    Floating point argument
  359. # (    Non-negative floating point argument
  360. # )    Positive floating point argument
  361. # #    Integer argument
  362. # <    Non-negative integer argument
  363. # >    Positive integer argument
  364. # The only difference the type of argument makes is in the runtime argument
  365. # error checking that is done.
  366.  
  367. # The & option is a special case used to get numeric options without the
  368. # user having to give an option character.  It is shorthand for [-+.0-9].
  369. # If & is included in optlist and an option string that begins with one of
  370. # these characters is seen, the value given to "&" will include the first
  371. # char of the option.  & must be followed by a type character other than ":"
  372. # or ";".
  373. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  374.  
  375. # Strings in argv[] which begin with "-" or "+" are taken to be
  376. # strings of options, except that a string which consists solely of "-"
  377. # or "+" is taken to be a non-option string; like other non-option strings,
  378. # it stops the scanning of argv and is left in argv[].
  379. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  380. # If an option takes an argument, the argument may either immediately
  381. # follow it or be given separately.
  382. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  383. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  384. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  385. # this feature had a flaw that caused problems in some cases.  See the OptChars
  386. # parameter to explicitly set the option-specifier characters.
  387.  
  388. # If an option that does not take an argument is given,
  389. # an index with its name is created in Options and its value is set to the
  390. # number of times it occurs in argv[].
  391.  
  392. # If an option that does take an argument is given, an index with its name is
  393. # created in Options and its value is set to the value of the argument given
  394. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  395. # If an option that takes an argument is given more than once,
  396. # Options[option-name,"count"] is incremented, and the value is assigned to
  397. # the index (option-name,instance) where instance is 2 for the second occurance
  398. # of the option, etc.
  399. # In other words, the first time an option with a value is encountered, the
  400. # value is assigned to an index consisting only of its name; for any further
  401. # occurances of the option, the value index has an extra (count) dimension.
  402.  
  403. # The sequence number for each option found in argv[] is stored in
  404. # Options[option-name,"num",instance], where instance is 1 for the first
  405. # occurance of the option, etc.  The sequence number starts at 1 and is
  406. # incremented for each option, both those that have a value and those that
  407. # do not.  Options set from a config file have a value of 0 assigned to this.
  408.  
  409. # Options and their arguments are deleted from argv.
  410. # Note that this means that there may be gaps left in the indices of argv[].
  411. # If compress is nonzero, argv[] is packed by moving its elements so that
  412. # they have contiguous integer indices starting with 0.
  413. # Option processing will stop with the first unrecognized option, just as
  414. # though -- was given except that unlike -- the unrecognized option will not be
  415. # removed from ARGV[].  Normally, an error value is returned in this case.
  416. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  417. # be found, so the number of remaining arguments is returned instead.
  418. # If OptChars is not a null string, it is the set of characters that indicate
  419. # that an argument is an option string if the string begins with one of the
  420. # characters.  A string consisting solely of two of the same option-indicator
  421. # characters stops the scanning of argv[].  The default is "-+".
  422. # argv[0] is not examined.
  423. # The number of arguments left in argc is returned.
  424. # If an error occurs, the global string OptErr is set to an error message
  425. # and a negative value is returned.
  426. # Current error values:
  427. # -1: option that required an argument did not get it.
  428. # -2: argument of incorrect type supplied for an option.
  429. # -3: unrecognized (invalid) option.
  430. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  431. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  432. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  433. {
  434. # ArgNum is the index of the argument being processed.
  435. # ArgsLeft is the number of arguments left in argv.
  436. # Arg is the argument being processed.
  437. # ArgLen is the length of the argument being processed.
  438. # ArgInd is the position of the character in Arg being processed.
  439. # Option is the character in Arg being processed.
  440. # Pos is the position in OptList of the option being processed.
  441. # NumOpt is true if a numeric option may be given.
  442.     ArgsLeft = argc
  443.     NumOpt = index(OptList,"&")
  444.     OptionNum = 0
  445.     if (OptChars == "")
  446.     OptChars = "-+"
  447.     while (OptChars != "") {
  448.     c = substr(OptChars,1,1)
  449.     OptChars = substr(OptChars,2)
  450.     OptCharSet[c]
  451.     OptTerm[c c]
  452.     }
  453.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  454.     Arg = argv[ArgNum]
  455.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  456.         break    # Not an option; quit
  457.     if (Arg in OptTerm) {
  458.         delete argv[ArgNum]
  459.         ArgsLeft--
  460.         break
  461.     }
  462.     ArgLen = length(Arg)
  463.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  464.         Option = substr(Arg,ArgInd,1)
  465.         if (NumOpt && Option ~ /[-+.0-9]/) {
  466.         # If this option is a numeric option, make its flag be & and
  467.         # its option string flag position be the position of & in
  468.         # the option string.
  469.         Option = "&"
  470.         Pos = NumOpt
  471.         # Prefix Arg with a char so that ArgInd will point to the
  472.         # first char of the numeric option.
  473.         Arg = "&" Arg
  474.         ArgLen++
  475.         }
  476.         # Find position of flag in option string, to get its type (if any).
  477.         # Disallow & as literal flag.
  478.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  479.         if (AllowUnrecOpt) {
  480.             Escape = 1
  481.             break
  482.         }
  483.         else {
  484.             OptErr = "Invalid option: " specGiven Option
  485.             return -3
  486.         }
  487.         }
  488.  
  489.         # Find what the value of the option will be if it takes one.
  490.         # NeedNextOpt is true if the option specifier is the last char of
  491.         # this arg, which means that if the option requires a value it is
  492.         # the next arg.
  493.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  494.         if (GotValue = ArgNum + 1 < argc)
  495.             Value = argv[ArgNum+1]
  496.         }
  497.         else {    # Value is included with option
  498.         Value = substr(Arg,ArgInd + 1)
  499.         GotValue = 1
  500.         }
  501.  
  502.         if (HadValue = AssignVal(Option,Value,Options,
  503.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  504.         specGiven)) {
  505.         if (HadValue < 0)    # error occured
  506.             return HadValue
  507.         if (HadValue == 2)
  508.             ArgInd++    # Account for the single-char value we used.
  509.         else {
  510.             if (NeedNextOpt) {    # option took next arg as value
  511.             delete argv[++ArgNum]
  512.             ArgsLeft--
  513.             }
  514.             break    # This option has been used up
  515.         }
  516.         }
  517.     }
  518.     if (Escape)
  519.         break
  520.     # Do not delete arg until after processing of it, so that if it is not
  521.     # recognized it can be left in ARGV[].
  522.     delete argv[ArgNum]
  523.     ArgsLeft--
  524.     }
  525.     if (compress != 0) {
  526.     dest = 1
  527.     src = argc - ArgsLeft + 1
  528.     for (count = ArgsLeft - 1; count; count--) {
  529.         ARGV[dest] = ARGV[src]
  530.         dest++
  531.         src++
  532.     }
  533.     }
  534.     return ArgsLeft
  535. }
  536.  
  537. # Assignment to values in Options[] occurs only in this function.
  538. # Option: Option specifier character.
  539. # Value: Value to be assigned to option, if it takes a value.
  540. # Options[]: Options array to return values in.
  541. # ArgType: Argument type specifier character.
  542. # GotValue: Whether any value is available to be assigned to this option.
  543. # Name: Name of option being processed.
  544. # OptionNum: Number of this option (starting with 1) if set in argv[],
  545. #     or 0 if it was given in a config file or in the environment.
  546. # SingleOpt: true if the value (if any) that is available for this option was
  547. #     given as part of the same command line arg as the option.  Used only for
  548. #     options from the command line.
  549. # specGiven is the option specifier character use, if any (e.g. - or +),
  550. # for use in error messages.
  551. # Global variables: OptErr
  552. # Return value: negative value on error, 0 if option did not require an
  553. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  554. # the arg.
  555. # Current error values:
  556. # -1: Option that required an argument did not get it.
  557. # -2: Value of incorrect type supplied for option.
  558. # -3: Bad type given for option &
  559. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  560. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  561.     # If option takes a value...    [
  562.     NumTypes = "*()#<>]"
  563.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  564.     OptErr = "Bad type given for & option"
  565.     return -3
  566.     }
  567.  
  568.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  569.     if (!GotValue) {
  570.         if (Name != "")
  571.         OptErr = "Variable requires a value -- " Name
  572.         else
  573.         OptErr = "option requires an argument -- " Option
  574.         return -1
  575.     }
  576.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  577.         OptErr = Err
  578.         return -2
  579.     }
  580.     # Mark this as a numeric variable; will be propogated to Options[] val.
  581.     if (ArgType != ":" && ArgType != ";")
  582.         Value += 0
  583.     if ((Instance = ++Options[Option,"count"]) > 1)
  584.         Options[Option,Instance] = Value
  585.     else
  586.         Options[Option] = Value
  587.     }
  588.     # If this is an environ or rcfile assignment & it was given a value...
  589.     else if (!OptionNum && Value != "") {
  590.     UsedValue = 1
  591.     # If the value is "0" or "-" and this is the first instance of it,
  592.     # do not set Options[Option]; this allows an assignment in an rcfile to
  593.     # turn off an option (for the simple "Option in Options" test) in such
  594.     # a way that it cannot be turned on in a later file.
  595.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  596.         Instance = 1
  597.     else
  598.         Instance = ++Options[Option]
  599.     # Save the value even though this is a flag
  600.     Options[Option,Instance] = Value
  601.     }
  602.     # If this is a command line flag and has a - following it in the same arg,
  603.     # it is being turned off.
  604.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  605.     UsedValue = 2
  606.     if (Option in Options)
  607.         Instance = ++Options[Option]
  608.     else
  609.         Instance = 1
  610.     Options[Option,Instance]
  611.     }
  612.     # If this is a flag assignment without a value, increment the count for the
  613.     # flag unless it was turned off.  The indicator for a flag being turned off
  614.     # is that the flag index has not been set in Options[] but it has an
  615.     # instance count.
  616.     else if (Option in Options || !((Option,1) in Options))
  617.     # Increment number of times this flag seen; will inc null value to 1
  618.     Instance = ++Options[Option]
  619.     Options[Option,"num",Instance] = OptionNum
  620.     return UsedValue
  621. }
  622.  
  623. # Option is the option letter
  624. # Value is the value being assigned
  625. # Name is the var name of the option, if any
  626. # ArgType is one of:
  627. # :    String argument
  628. # ;    Non-null string argument
  629. # *    Floating point argument
  630. # (    Non-negative floating point argument
  631. # )    Positive floating point argument
  632. # #    Integer argument
  633. # <    Non-negative integer argument
  634. # >    Positive integer argument
  635. # specGiven is the option specifier character use, if any (e.g. - or +),
  636. # for use in error messages.
  637. # Returns null on success, err string on error
  638. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  639.     if (ArgType == ":")
  640.     return ""
  641.     if (ArgType == ";") {
  642.     if (Value == "")
  643.         Err = "must be a non-empty string"
  644.     }
  645.     # A number begins with optional + or -, and is followed by a string of
  646.     # digits or a decimal with digits before it, after it, or both
  647.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  648.     Err = "must be a number"
  649.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  650.     Err = "may not include a fraction"
  651.     else if (ArgType ~ "[()<>]" && Value < 0)
  652.     Err = "may not be negative"
  653.     # (
  654.     else if (ArgType ~ "[)>]" && Value == 0)
  655.     Err = "must be a positive number"
  656.     if (Err != "") {
  657.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  658.     if (Name != "")
  659.         return ErrStr "variable " substr(Name,1,1) " " Err
  660.     else {
  661.         if (Option == "&")
  662.         Option = Value
  663.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  664.     }
  665.     }
  666.     else
  667.     return ""
  668. }
  669.  
  670. # Note: only the above functions are needed by ProcArgs.
  671. # The rest of these functions call ProcArgs() and also do other
  672. # option-processing stuff.
  673.  
  674. # Opts: Process command line arguments.
  675. # Opts processes command line arguments using ProcArgs()
  676. # and checks for errors.  If an error occurs, a message is printed
  677. # and the program is exited.
  678. #
  679. # Input variables:
  680. # Name is the name of the program, for error messages.
  681. # Usage is a usage message, for error messages.
  682. # OptList the option description string, as used by ProcArgs().
  683. # MinArgs is the minimum number of non-option arguments that this
  684. # program should have, non including ARGV[0] and +h.
  685. # If the program does not require any non-option arguments,
  686. # MinArgs should be omitted or given as 0.
  687. # rcFiles, if given, is a colon-seprated list of filenames to read for
  688. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  689. # by the value of the environment variable HOME.  If a filename begins with
  690. # $, the part from the character after the $ up until (but not including)
  691. # the first character not in [a-zA-Z0-9_] will be searched for in the
  692. # environment; if found its value will be substituted, if not the filename will
  693. # be discarded.
  694. # rcfiles are read in the order given.
  695. # Values given in them will not override values given on the command line,
  696. # and values given in later files will not override those set in earlier
  697. # files, because AssignVal() will store each with a different instance index.
  698. # The first instance of each variable, either on the command line or in an
  699. # rcfile, will be stored with no instance index, and this is the value
  700. # normally used by programs that call this function.
  701. # VarNames is a comma-separated list of variable names to map to options,
  702. # in the same order as the options are given in OptList.
  703. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  704. # searched for in the environment.  If set to -1, all values will be searched
  705. # for in the environment.  Values given in the environment will override
  706. # those given in the rcfiles but not those given on the command line.
  707. # NoRCopt, if given, is an additional letter option that if given on the
  708. # command line prevents the rcfiles from being read.
  709. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  710. # ExclusiveOptions() for a description of exOpts.
  711. # Special options:
  712. # If x is made an option and is given, some debugging info is output.
  713. # h is assumed to be the help option.
  714.  
  715. # Global variables:
  716. # The command line arguments are taken from ARGV[].
  717. # The arguments that are option specifiers and values are removed from
  718. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  719. # The number of elements in ARGV[] should be in ARGC.
  720. # After processing, ARGC is set to the number of elements left in ARGV[].
  721. # The option values are put in Options[].
  722. # On error, Err is set to a positive integer value so it can be checked for in
  723. # an END block.
  724. # Return value: The number of elements left in ARGV is returned.
  725. # Must keep OptErr global since it may be set by InitOpts().
  726. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  727. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  728.     if (MinArgs == "")
  729.     MinArgs = 0
  730.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  731.     optChars)
  732.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  733.     if (ArgsLeft >= 0) {
  734.         OptErr = "Not enough arguments"
  735.         Err = 4
  736.     }
  737.     else
  738.         Err = -ArgsLeft
  739.     printf "%s: %s.\nUse -h for help.\n%s\n",
  740.     Name,OptErr,Usage > "/dev/stderr"
  741.     exit 1
  742.     }
  743.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  744.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  745.     {
  746.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  747.     Err = -e
  748.     exit 1
  749.     }
  750.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  751.     {
  752.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  753.     Err = 1
  754.     exit 1
  755.     }
  756.     return ArgsLeft
  757. }
  758.  
  759. # ReadConfFile(): Read a file containing var/value assignments, in the form
  760. # <variable-name><assignment-char><value>.
  761. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  762. # line and whitespace between the variable name and the assignment character) 
  763. # is stripped.  Lines that do not contain an assignment operator or which
  764. # contain a null variable name are ignored, other than possibly being noted in
  765. # the return value.  If more than one assignment is made to a variable, the
  766. # first assignment is used.
  767. # Input variables:
  768. # File is the file to read.
  769. # Comment is the line-comment character.  If it is found as the first non-
  770. #     whitespace character on a line, the line is ignored.
  771. # Assign is the assignment string.  The first instance of Assign on a line
  772. #     separates the variable name from its value.
  773. # If StripWhite is true, whitespace around the value (whitespace between the
  774. #     assignment char and trailing whitespace on the line) is stripped.
  775. # VarPat is a pattern that variable names must match.  
  776. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  777. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  778. #     a line; no assignment operator is needed.  These variables are set in
  779. #     the output array with a null value.  Lines containing nothing but
  780. #     whitespace are still ignored.
  781. # Output variables:
  782. # Values[] contains the assignments, with the indexes being the variable names
  783. #     and the values being the assigned values.
  784. # Lines[] contains the line number that each variable occured on.  A flag set
  785. #     is record by giving it an index in Lines[] but not in Values[].
  786. # Return value:
  787. # If any errors occur, a string consisting of descriptions of the errors
  788. # separated by newlines is returned.  In no case will the string start with a
  789. # numeric value.  If no errors occur,  the number of lines read is returned.
  790. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  791. FlagsOK,
  792. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  793.     if (Comment != "")
  794.     Comment = "^" Comment
  795.     AssignLen = length(Assign)
  796.     if (VarPat == "")
  797.     VarPat = "."    # null varname not allowed
  798.     while ((Status = (getline Line < File)) == 1) {
  799.     LineNum++
  800.     sub("^[ \t]+","",Line)
  801.     if (Line == "")        # blank line
  802.         continue
  803.     if (Comment != "" && Line ~ Comment)
  804.         continue
  805.     if (Pos = index(Line,Assign)) {
  806.         Var = substr(Line,1,Pos-1)
  807.         Val = substr(Line,Pos+AssignLen)
  808.         if (StripWhite) {
  809.         sub("^[ \t]+","",Val)
  810.         sub("[ \t]+$","",Val)
  811.         }
  812.     }
  813.     else {
  814.         Var = Line    # If no value, var is entire line
  815.         Val = ""
  816.     }
  817.     if (!FlagsOK && Val == "") {
  818.         Errs = Errs \
  819.         sprintf("\nBad assignment on line %d of file %s: %s",
  820.         LineNum,File,Line)
  821.         continue
  822.     }
  823.     sub("[ \t]+$","",Var)
  824.     if (Var !~ VarPat) {
  825.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  826.         LineNum,File,Var)
  827.         continue
  828.     }
  829.     if (!(Var in Lines)) {
  830.         Lines[Var] = LineNum
  831.         if (Pos)
  832.         Values[Var] = Val
  833.     }
  834.     }
  835.     if (Status)
  836.     Errs = Errs "\nCould not read file " File
  837.     close(File)
  838.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  839. }
  840.  
  841. # Variables:
  842. # Data is stored in Options[].
  843. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  844. # Global vars:
  845. # Sets OptErr.  Uses ENVIRON[].
  846. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  847. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  848. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  849. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  850.     split("",filesRead,"")    # make awk know this is an array
  851.     NumVars = split(VarNames,Vars,",")
  852.     TypesInd = Ret = 0
  853.     if (EnvSearch == -1)
  854.     EnvSearch = NumVars
  855.     for (i = 1; i <= NumVars; i++) {
  856.     Var = Vars[i]
  857.     CharOpt = substr(OptList,++TypesInd,1)
  858.     if (CharOpt ~ "^[:;*()#<>&]$")
  859.         CharOpt = substr(OptList,++TypesInd,1)
  860.     Map[Var] = CharOpt
  861.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  862.     # Do not overwrite entries from environment
  863.     if (i <= EnvSearch && Var in ENVIRON &&
  864.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  865.         return Err
  866.     }
  867.  
  868.     numrcFiles = split(rcFiles,fNames,":")
  869.     for (i = 1; i <= numrcFiles; i++) {
  870.     rcFile = fNames[i]
  871.     if (rcFile ~ "^~/")
  872.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  873.     else if (rcFile ~ /^\$/) {
  874.         rcFile = substr(rcFile,2)
  875.         match(rcFile,"^[a-zA-Z0-9_]*")
  876.         envvar = substr(rcFile,1,RLENGTH)
  877.         if (envvar in ENVIRON)
  878.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  879.         else
  880.         continue
  881.     }
  882.     if (rcFile in filesRead)
  883.         continue
  884.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  885.     # may be the same
  886.     filesRead[rcFile]
  887.     if ("x" in Options)
  888.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  889.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  890.     if (retStr > 0)
  891.         READ_RCFILE = 1
  892.     else if (ret != "") {
  893.         OptErr = retStr
  894.         Ret = -1
  895.     }
  896.     for (Var in Lines)
  897.         if (Var in Map) {
  898.         if ((Err = AssignVal(Map[Var],
  899.         Var in Values ? Values[Var] : "",Options,Types[Var],
  900.         Var in Values,Var,0)) < 0)
  901.             return Err
  902.         }
  903.         else {
  904.         OptErr = sprintf(\
  905.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  906.         Lines[Var],rcFile)
  907.         Ret = -1
  908.         }
  909.     }
  910.  
  911.     if ("x" in Options)
  912.     for (Var in Map)
  913.         if (Map[Var] in Options)
  914.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  915.         "/dev/stderr"
  916.         else
  917.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  918.     return Ret
  919. }
  920.  
  921. # OptSets is a semicolon-separated list of sets of option sets.
  922. # Within a list of option sets, the option sets are separated by commas.  For
  923. # each set of sets, if any option in one of the sets is in Options[] AND any
  924. # option in one of the other sets is in Options[], an error string is returned.
  925. # If no conflicts are found, nothing is returned.
  926. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  927. # the exclusions presented by the first set of sets (ab,def,g) if:
  928. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  929. # (a or b is in Options[]) AND (g is in Options) OR
  930. # (d, e, or f is in Options[]) AND (g is in Options)
  931. # An error will be returned due to the exclusions presented by the second set
  932. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  933. # todo: make options given on command line unset options given in config file
  934. # todo: that they conflict with.
  935. function ExclusiveOptions(OptSets,Options,
  936. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  937. SetNum,OSetNum) {
  938.     NumSetSets = split(OptSets,SetSets,";")
  939.     # For each set of sets...
  940.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  941.     # NumSets is the number of sets in this set of sets.
  942.     NumSets = split(SetSets[SetSet],Sets,",")
  943.     # For each set in a set of sets except the last...
  944.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  945.         s1 = Sets[SetNum]
  946.         L1 = length(s1)
  947.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  948.         # If any of the options in this set was given, check whether
  949.         # any of the options in the other sets was given.  Only check
  950.         # later sets since earlier sets will have already been checked
  951.         # against this set.
  952.         if ((c1 = substr(s1,Pos1,1)) in Options)
  953.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  954.             s2 = Sets[OSetNum]
  955.             L2 = length(s2)
  956.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  957.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  958.                 ErrStr = ErrStr "\n"\
  959.                 sprintf("Cannot give both %s and %s options.",
  960.                 c1,c2)
  961.             }
  962.     }
  963.     }
  964.     if (ErrStr != "")
  965.     return substr(ErrStr,2)
  966.     return ""
  967. }
  968.  
  969. # The value of each instance of option Opt that occurs in Options[] is made an
  970. # index of Set[].
  971. # The return value is the number of instances of Opt in Options.
  972. function Opt2Set(Options,Opt,Set,  count) {
  973.     if (!(Opt in Options))
  974.     return 0
  975.     Set[Options[Opt]]
  976.     count = Options[Opt,"count"]
  977.     for (; count > 1; count--)
  978.     Set[Options[Opt,count]]
  979.     return count
  980. }
  981.  
  982. # The value of each instance of option Opt that occurs in Options[] that
  983. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  984. # Other values are made indexes of Set[].
  985. # The return value is the number of instances of Opt in Options.
  986. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  987.     ret = Opt2Set(Options,Opt,aSet)
  988.     for (value in aSet)
  989.     if (substr(value,1,1) == "!")
  990.         nSet[substr(value,2)]
  991.     else
  992.         Set[value]
  993.     return ret
  994. }
  995.  
  996. # Returns true if option Opt was given on the command line.
  997. function CmdLineOpt(Options,Opt,  i) {
  998.     for (i = 1; (Opt,"num",i) in Options; i++)
  999.     if (Options[Opt,"num",i] != 0)
  1000.         return 1
  1001.     return 0
  1002. }
  1003. ### End of ProcArgs library
  1004.